Return to the table of contents


Basics of Programming

Creative Programming?

After that necessary detour into the workings of the hardware, we can now resume our regularly scheduled explanation of the creative possibilities of computers. It may sound odd to describe computers as providing grand scope for creative activities: Aren't they monotonous, dull, unintelligent, and extremely limited? Yes, they are. However, they have two redeeming virtues that make them ideal as the canvas of invention: They are extraordinarily fast and spectacularly reliable. These characteristics allow the creator of a program to weave intricate chains of thought and have a fantastic number of steps carried out without fail. We'll begin to explore how this is possible after we go over some definitions and objectives for this chapter.

Definitions

An identifier is a user defined name; variable names are identifiers. Identifiers must not conflict with keywords such as if and while; for example, you cannot create a variable with the name while.

A keyword is a word defined in the C++ language, such as if and while. It is illegal to define an identifier such as a variable name that conflicts with a keyword.

Objectives of This Chapter

By the end of this chapter, you should

  1. Understand what a program is and have some idea how a program works.
  2. Understand how to get information into and out of a program.
  3. Understand how to use if and while to control the execution of a program.1
  4. Understand how a portion of a program can be marked off so that it will be treated as one unit.
  5. Be able to read and understand a simple program I've written in C++.

Speed Demon

The most impressive attribute of modern computers, of course, is their speed; as we have already seen, this is measured in MIPS (millions of instructions per second).

Of course, raw speed is not very valuable if we can't rely on the results we get. ENIAC, one of the first electronic computers, had a failure every few hours, on the average; since the problems it was used to solve took about that much time to run, the likelihood that the results were correct wasn't very high. Particularly critical calculations were often run several times, and if the users got the same answer twice, they figured it was probably correct. By contrast, modern computers are almost incomprehensibly reliable. With almost any other machine, a failure rate of one in every million operations would be considered phenomenally low, but a computer with such a failure rate would make dozens of errors per second.2

Blaming It on the Computer

On the other hand, if computers are so reliable, why are they blamed for so much that goes wrong with modern life? Who among us has not been the victim of an erroneous credit report, or a bill sent to the wrong address, or been put on hold for a long time because "the computer is down"? The answer is fairly simple: It's almost certainly not the computer. More precisely, it's very unlikely that the CPU was at fault; it may be the software, other equipment such as telephone lines, tape or disk drives, or any of the myriad "peripheral devices" that the computer uses to store and retrieve information and interact with the outside world. Usually, it's the software; when customer service representatives tell you that they can't do something obviously reasonable, you can count on its being the software. For example, I once belonged to a 401K plan whose administrators provided statements only every three months, about three months after the end of the quarter; in other words, in July I found out how much my account had been worth at the end of March. The only way to estimate how much I had in the meantime was to look up the share values in the newspaper and multiply by the number of shares. Of course, the mutual fund that issued the shares could tell its shareholders their account balances at any time of the day or night; however, the company that administered the 401K plan didn't bother to provide such a service, as it would have required doing some work.3 Needless to say, whenever I hear that "the computer can't do that" as an excuse for such poor service, I reply "Then you need some different programmers."

That Does Not Compute

All of this emphasis on computation, however, should not blind us to the fact that computers are not solely arithmetic engines. The most common application for which PCs are used is word processing, which is hardly a hotbed of arithmetical calculation. While we have so far considered only numeric data, this is a good illustration of the fact that computers also deal with another kind of information, which is commonly referred to by the imaginative term nonnumeric variables. Numeric variables are those suited for use in calculations, such as in totalling a set of weights. On the other hand, nonnumeric data are items that are not used in calculations like adding, multiplying, or subtracting: Examples are names, addresses, telephone numbers, Social Security numbers, bank account numbers, or drivers license numbers. Note that just because something is called a number, or even is composed entirely of the digits 0-9, does not make it numeric data by our standards. The question is how the item is used. No one adds, multiplies, or subtracts drivers license numbers, for example; they serve solely as identifiers and could just as easily have letters in them, as indeed some do.

For the present, though, let's stick with numeric variables. Now that we have defined a couple of types of these variables, short and unsigned short, what can we do with them? To do anything with them, we have to write a C++ program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into machine instructions. This raises an interesting point: Why does our C++ program have to be translated into machine instructions? Isn't the computer's job to execute (or run) our program?

Lost in Translation

Yes, but it can't run a C++ program. The only kind of program any computer can run is one made of machine instructions; this is called a machine language program, for obvious reasons. Therefore, to get our C++ program to run, we have to translate it into a machine language program. Don't worry, you won't have to do it yourself; that's why we have a program called a compiler.4 The most basic tasks that the compiler performs are the following:

  1. Assigning memory addresses to variables. This allows us to use names for variables, rather than having to keep track of the address of each variable ourselves.
  2. Translating arithmetic and other operations (such as +, -, etc.) into the equivalent machine instructions, including the addresses of variables assigned in the previous step.5

This is probably a bit too abstract to be easily grasped, so let's look at an example as soon as we have defined some terms. Each complete operation understood by the compiler is called a statement, and ends with a semicolon (;).6 Figure littlenumeric shows some sample statements that do arithmetic calculations.7

A little numeric calculation (Figure littlenumeric)

short i;

short j;

short k;

short m;

i = 5;

j = i * 3; // j is now 15

k = j - i; // k is now 10

m = (k + j) / 5; // m is now 5

i = i + 1; // i is now 6

To enter such statements in the first place, you can use any text editor that generates "plain" text files, such as the EDIT program that comes with DOS or Windows' Notepad. Whichever text editor you use, make sure that it produces files that contain only what you type; stay away from programs like Windows WriteTM or Word for WindowsTM, as they add some of their own information to indicate fonts, type sizes, and the like to your file, which will foul up the compiler.

Once we have entered the statements for our program, we use the compiler, as indicated, to translate the programs we write into a form that the computer can perform; as defined in Chapter prologue.htm, the form we create is called source code, since it is the source of the program logic, while the form of our program that the computer can execute is called an executable program, or just an executable for short.

As I've mentioned before, there are several types of variables, the short being only one of these types. Therefore, the compiler needs some explanatory material so that it can tell what types of variables you're using; that's what the first four lines of our little sample program fragment are for. Each line tells the compiler that the type of the variable i, j, k, or m is short; that is, it can contain values from -32768 to +32767.8 After this introductory material, we move into the list of operations to be performed. This is called the executable portion of the program, as it actually causes the computer to do something when the program is executed; the operations to be performed, as mentioned above, are called statements. The first one, i = 5;, sets the variable i to the value 5. A value such as 5, which doesn't have a name, but represents itself in a literal manner, is called (appropriately enough) a literal value.

This is as good a time as any for me to mention something that experienced C programmers take for granted but has a tendency to confuse novices. This is the choice of the = sign to indicate the operation of setting a variable to a value, which is known technically as assignment. As far as I'm concerned, an assignment operation would be more properly indicated by some symbol suggesting movement of data, such as 5 => i;, meaning "store the value 5 into variable i". Unfortunately, it's too late to change the notation for the assignment statement, as such a statement is called, so you'll just have to get used to it. The = means "set the variable on the left to the value on the right".9 Now that I've warned you about that possible confusion, let's continue looking at the operations in the program. The next one, j = i * 3;, specifies that the variable j is to be set to the result of multiplying the current value of i by the literal value 3. The one after that, k = j - i;, tells the computer to set k to the amount by which j is greater than i; that is, j - i. The most complicated line in our little program fragment, m = (k + j) / 5;, calculates m as the sum of adding k and j and dividing the result by the literal value 5. Finally, the line i = i + 1; sets i to the value of i plus the literal value 1.

This last may be somewhat puzzling; how can i be equal to i + 1? The answer is that an assignment statement is not an algebraic equality, no matter how much it may resemble one. It is a command telling the computer to assign a value to a variable. Therefore, what i = i + 1; actually means is "Take the current value of i, add 1 to it, and store the result back into i." In other words, a C++ variable is a place to store a value; the variable i can take on any number of values, but only one at a time; any former value is lost when a new one is assigned.

This notion of assignment was the topic of quite a few messages with Susan. Let's go to the first round:

Susan: I am confused with the statement i = i + 1; when you have stated previously that i = 5;. So, which one is it? How can there be two values for i?

Steve: There can't; that is, not at one time. However, i, like any other variable, can take on any number of values, one after another. First, we set it to 5; then we set it to 1 more than it was before (i + 1), so it ends up as 6.

Susan: Well, the example made it look as if the two values of i were available to be used by the computer at the same time. They were both lumped together as executable material.

Steve: After the statement i = 5;, and before the statement i = i + 1;, the value of i is 5. After the statement i = i + 1;, the value of i is 6. The key here is that a variable such as i is just our name for some area of memory that can hold only one value at one time. Does that clear it up?

Susan: So, it is not like algebra? Then i is equal to an address of memory and does not really equate with a numerical value? Well, I guess it does when you assign a numerical value to it. Is that it?

Steve: Very close. A variable in C++ isn't really like an algebraic variable, which has a value that has to be figured out and doesn't change in a given problem. A programming language variable is just a name for a storage location that can contain a value.

With any luck, that point has been pounded into the ground, so you won't have the same trouble that Susan did. Now let's look at exactly what an assignment statement does. If the value of i before the statement i = i + 1; is 5 (for example), then that statement will cause the CPU to perform the following steps:10

  1. Take the current value of i (5).
  2. Add one to that value (6).
  3. Store the result back into i.

After the execution of this statement, i will have the value 6.

What's Going on Underneath?

In a moment we're going to dive a little deeper into how the the CPU accomplishes its task of manipulating data, such as we are doing here with our arithmetic program. First, though, it's time for a little pep talk for those of you who might be wondering exactly why this apparent digression is necessary. It's because if you don't understand what is going on under the surface, you won't be able to get past the "Sunday driver" stage of programming in C++. In some languages it's neither necessary or perhaps even possible to find out what the computer actually does to execute your program, but C++ isn't one of them. A good C++ programmer needs an intimate acquaintance with the internal workings of the language, for reasons which will become very apparent when we get to Chapter string.htm. For the moment, you'll just have to take my word that working through these intricacies is essential; the payoff for a thorough grounding in these fundamental concepts of computing will be worth the struggle.

Now let's get to the task of exploring how the CPU actually stores and manipulates data in memory. As we saw previously, each memory location in RAM has a unique memory address; machine instructions that refer to RAM use this address to specify which byte or bytes of memory they wish to retrieve or modify. This is fairly straightforward in the case of a 1-byte variable, where the instruction merely specifies the byte that corresponds to the variable. On the other hand, the situation isn't quite as simple in the case of a variable that occupies more than 1 byte. Of course, no law of nature says that an instruction couldn't contain a number of addresses, one for each byte of the variable. However, this solution is never adopted in practice, as it would make instructions much longer than they need to be. Instead, the address in such an instruction specifies the first byte of RAM occupied by the variable, and the other bytes are assumed to follow immediately after the first one. For example, in the case of a short variable, which as we have seen occupies 2 bytes of RAM, the instruction would specify the address of the first byte of the area of RAM in which the variable is stored. However, there's one point that I haven't brought up yet: how the data for a given variable are actually arranged in memory. For example, suppose that the contents of a small section of RAM (specified as two hex digits per byte) look like Figure ram.value.

A small section of RAM (Figure ram.value)

Address    Hex byte value

1000 41

1001 42

1002 43

1003 44

1004 00

Also suppose that a short variable i is stored starting at address 1000. To do much with a variable, we're going to have to load it into a general register, one of the small number of named data storage locations in the CPU intended for general use by the programmer; this proximity allows the CPU to operate on data in the registers at maximum speed. You may recall that there are seven general registers in the 386 CPU (and its successors); they're named eax, ebx, ecx, edx, esi, edi, and ebp.11 Unfortunately, there's another complication here; these registers are designed to operate on 4-byte quantities, while our variable i, being of type short, is only two bytes long. Are we out of luck? No, but we do have to specify how long the variable is that we want to load. This problem is not unique to Intel CPUs, since any CPU has to have the ability to load different-sized variables into registers. Different CPUs use different methods of specifying this important piece of information; in the Intel CPUs, one way to do this is to alter the register name.12 As we saw in the discussion of the development of Intel machines, we can remove the leading e from the register name to specify that we're dealing with 2-byte values; the resulting name refers to the lower two bytes of the 4-byte register. Therefore, if we wanted to load the value of i into register ax (that is, the lower half of register eax), the instruction could be written as follows:13 mov ax,[1000]14 As usual, our resident novice Susan had some questions on this topic. Here is our conversation:

Susan: If you put something into 1000 that is "too big" for it, then it spills over to the next address?

Steve: Sort of. When you "put something into 1000", you have to specify exactly what it is you're "putting in". That is, it must be either a short, a char, or some other type of variable that has a defined size.

Susan: Is that how it works? Why then is it not necessary to specify that it is going to have to go into 1000 and 1001? So what you put in is not really in 1000 anymore, it is in 1000 and 1001? How do you refer to its REAL address? What if there is no room in 1001? Would it go to 2003 if that is the next available space?

Steve: Because the rule is that you always specify the starting address of any item (variable or constant) that is too big to fit in 1 byte. The other bytes of the item are always stored immediately following the address you specify. No bytes will be skipped when storing (or loading) one item; if the item needs 4 bytes and is to be stored starting at 1000, it will be stored in 1000-1003.

Susan: I see. In other words, the compiler will always use the next bytes of RAM, however many need to be used to store the item?

Steve: Right.

Who's on First?

Now I have a question for you. After we execute the assembly language statement mov ax,[1000] to load the value of i into ax, what's in register ax? That may seem like a silly question; the answer is obviously the value of i. Yes, but what is that value exactly? The first byte of i, at location 1000, has the value 41 hexadecimal (abbreviated 41h), and the second byte, at location 1001, has the value 42h. But the value of i is 2 bytes long; is it 4142h or 4241h? These are clearly not the same!

That was a trick question; there's no way for you to deduce the answer with only the information I've given you so far. The answer happens to be 4241h, because that's the way Intel decided to do it; that is, the low part of the value is stored in the byte of RAM where the variable starts. Some other CPUs do it the opposite way, where the high part of the value is stored in the byte of RAM where the variable starts; this is called big-endian, since the big end of the value is first, while the Intel way is correspondingly called little-endian. And some machines, such as the Power PC, can use either of these methods according to how they are started up. This makes it easier for them to run software written for either memory orientation.

As you might have surmised, the same system applies to 4-byte values; therefore, if we wrote the instruction mov eax,[1000], since we're on a little-endian machine, it would load the eax register with the value 44434241h; that is, the four bytes 41, 42, 43, and 44 (hex) would be loaded into the eax register, with the byte having the lowest address loaded into the low end of the register.

Here's another example. A little-endian system would represent the number 1234 (hex) stored at address 5000 as in Figure littleendian.

One little endian (Figure littleendian)

Address   Value

5000 34

5001 12

whereas a big-endian system would represent the same value 1234 (hex) as illustrated in Figure bigendian.

A big endian example (Figure bigendian)

Address   Value

5000 12

5001 34

This really isn't much of a problem as long as we don't try to move data from one type of machine to another; however, when such data transportation is necessary, dealing with mixed endianness can be a real nuisance. Before going on, let's practice a bit with this notion of how data are stored in memory.

Exercises, First Set

1. Assume that a short variable named z starts at location 1001 in a little-endian machine. Using Figure basicsex1 for the contents of memory, what is the value of z, in hex?

Exercise 1 (Figure basicsex1)

Address    Hex byte value

1000 3a

1001 43

1002 3c

1003 99

1004 00

Underware?

I can almost hear the wailing and tooth gnashing out there. Do I expect you to deal with all of these instructions and addresses by yourself? You'll undoubtedly be happy to learn that this isn't necessary, as the compiler takes care of these details. However, if you don't have some idea of how a compiler works, you'll be at a disadvantage when you're trying to figure out how to make it do what you want. Therefore, we're going to spend the next few pages "playing compiler"; that is, I'll examine each statement and indicate what action the compiler might take as a result. I'll simplify the statements a bit to make the explanation simpler; you should still get the idea. Figure reallylittle illustrates the set of statements that I'll compile:15

A really little numeric calculation (Figure reallylittle)

short i;

short j;

i = 5;

j = i + 3;

Compiler's Eye View

Here are the rules of this game:

  1. All numbers in the C++ program are decimal; all addresses and numbers in the machine instructions are hexadecimal.16
  2. All addresses are 2 bytes long.17
  3. Variables are stored at addresses starting at 1000.
  4. Machine instructions are stored at addresses starting at 2000.18
  5. A number not enclosed in [] is a literal value, which represents itself. For example, the instruction mov ax,1000 means to move the value 1000 into the ax register.
  6. A number enclosed in [] is an address, which specifies where data are to be stored or retrieved. For example, the instruction mov ax,[1000] means to move 2 bytes of data starting at location 1000, not the value 1000 itself, into the ax register.

Now, let's start compiling. The first statement, short i; tells me to allocate storage for a 2-byte variable called i that will be treated as signed (because that's the default). Since no value has been assigned to this variable yet, the resulting "memory map" looks like Figure compile1.

Compiling, part 1 (Figure compile1)

Address    Source code variable name

1000         i

As you might have guessed, this exercise was the topic of a considerable amount of discussion with Susan. Here's how it started:

Susan: So the first thing we do with a variable is to tell the address that its name is i, but no one is home, right? It has to get ready to accept a value. Could you put a value in it without naming it, just saying address 1000 has a value of 5? Why does it have to be called i first?

Steve: The reason that we use names instead of addresses is because it's much easier for people to keep track of names than it is to keep track of addresses. Thus, one of the main functions of a compiler is to allow us to use names that are translated into addresses for the computer's use.

The second statement, short j; tells me to allocate storage for a 2-byte variable called j that will be treated as signed (because that's the default). Since no value has been assigned to this variable yet, the resulting "memory map" looks like Figure compile2.

Compiling, part 2 (Figure compile2)

Address    Source code variable name

1000         i                
1002         j

Here's the exchange about this step:

Susan: Why isn't the address for j 1001?

Steve: Because a short is 2 bytes, not 1. Therefore, if i is at address 1000, j can't start before 1002; otherwise, the second byte of i would have the same address as the first byte of j, which would cause chaos in the program. Imagine changing i and having j change by itself.

Susan: Okay. I just thought that each address represented 2 bytes for some reason. Then in reality each address always has just 1 byte?

Steve: Every byte of RAM has a distinct address, and there is one address for each byte of RAM. However, it is often necessary to read or write more than one byte at a time, as in the case of a short, which is 2 bytes in length. The machine instructions that read or write more than 1 byte specify only the address of the first byte of the item to be read or written; the other byte or bytes of that item follow the first byte immediately in memory.

Susan: Okay, this is why I was confused. I thought when you specified that the RAM address 1000 was a short (2 bytes), it just made room for 2 bytes. So when you specify address 1000 as a short, you know that 1001 will also be occupied with what you put in 1000.

Steve: Or to be more precise, location 1001 will contain the second byte of the short value that starts in byte 1000.

The next line is blank, so we skip it. This brings us to the statement i = 5; which is an executable statement, so we need to generate one or more machine instructions to execute it. We have already assigned address 1000 to i, so we have to generate instructions that will set the 2 bytes at address 1000 to the value that represents 5. One way to do this is to start by setting ax to 5, by the instruction mov ax,5, then storing the contents of ax (5, of course) into the location where the value of i is kept, namely 1000, via the instruction mov [1000],ax.

Figure compile3 shows what our "memory map" looks like so far.

Compiling, part 3 (Figure compile3)

Address    Variable Name

1000         i                
1002         j

Address    Machine Instruction        Assembly Language                 
                                      Equivalent                
      +--------------------+                 
2000  |  b8 05 00          |    mov ax,5                
      +--------------------+                 
2003  |  a9 00 10          |    mov [1000],ax                
      +--------------------+ 

Here's the next installment of my discussion with Susan on this topic:

Susan: When you use ax in an instruction, that is a register, not RAM?

Steve: Yes.

Susan: How do you know you want that register and not another one? What are the differences in the registers? Is ax the first register that data will go into?

Steve: For our current purposes, all of the 16-bit general registers (ax, bx, cx, dx, si, di, bp) are the same. Some of them have other uses, but all of them can be used for simple arithmetic such as we're doing here.

Susan: How do you know that you are not overwriting something more important than what you are presently writing?

Steve: In assembly language, the programmer has to keep track of that; in the case of a compiled language, the compiler takes care of it instead, which is another reason to use a compiler rather than writing assembly language programs yourself.

Susan: If it overwrites, you said important data will go somewhere else. How will you know where it went? How does it know whether what is being overwritten is important? Wait. If something is overwritten, it isn't gone, is it? It is just moved, right?

Steve: The automatic movement of data that you're referring to applies only to cached data being transferred to RAM. That is, if a slot in the cache is needed, the data that it previously held is written out to RAM without the programmer's intervention. However, the content of registers is explicitly controlled by the programmer (or the compiler, in the case of a compiled language). If you write something into a register, whatever was there before is gone. So don't do that if you need the previous contents!

Susan: How do you know that 5 will require 2 bytes?

Steve: In C++, because it's a short. In assembly language, because I'm loading it into ax, which is a 2-byte register.

Susan: Why do the the variable addresses start at 1000 and the machine addresses start at 2000?

Steve: It's arbitrary; I picked those numbers out of the air. In a real program, the compiler decides where to put things.

Susan: What do you mean by machine address? What is the machine? Where are the machine addresses?

Steve: A machine address is a RAM address. The machine is the CPU. Machine addresses are stored in the instructions so the CPU knows which RAM location we're referring to.

Susan: We talked about storing instructions before; is this what we are doing here? Are those instructions the "machine instructions"?

Steve: Yes.

Susan: Now, this may sound like a very dumb question, but please tell me where 5 comes from? I mean if you are going to move the value of 5 into the register ax, where is 5 hiding to take it from and to put it in ax? Is it stored somewhere in memory that has to be moved, or is it simply a function of the user just typing in that value?

Steve: It is stored in the instruction as a literal value. If you look at the assembly language illustration on page , you will see that the mov ax,5 instruction translates into the three bytes b8 05 00; the 05 00 is the 5 in "little-endian" notation.

Susan: Now, what is so magical about ax (or any register for that matter) that will transform the address 1000 to hold the value of 5?

Steve: The register doesn't do it; the execution of the instruction mov [1000],ax is what sets the memory starting at address 1000 to the value 5.

Susan: What are those numbers supposed to be in the machine instruction box? Those are bytes? Bytes of what? Why are they there? What do they do?

Steve: They represent the actual machine language program as it is executed by the CPU. This is where "the rubber meets the road". All of our C++ or even assembly language programs have to be translated into machine language before they can be executed by the CPU.

Susan: So this is where 5 comes from? I can't believe that there seems to be more code. What is b8 supposed to be? Is it some other type of machine language?

Steve: Machine language is exactly what it is. The first byte of each instruction is the "operation code", or "op code" for short. That tells the CPU what kind of instruction to execute; in this case, b8 specifies a "load register ax with a literal value" instruction. The literal value is the next 2 bytes, which represent the value 5 in "little-endian" notation; therefore, the full translation of the instruction is "load ax with the literal value 5".

Susan: So that is the "op code"? Okay, this makes sense. I don't like it, but it makes sense. Will the machine instructions always start with an op code?

Steve: Yes, there's always an op code first; that's what tells the CPU what the rest of the bytes in the instruction mean.

Susan: Then I noticed that the remaining bytes seem to hold either a literal value or a variable address. Are those the only possibilities?

Steve: Those are the ones that we will need to concern ourselves with.

Susan: I don't understand why machine addresses aren't in 2-byte increments like variable addresses.

Steve: Variable addresses aren't always in 2-byte increments either; it just happens that short variables take up 2 bytes. Other kinds of variables can and often do have other lengths.

Susan: So even though variable addresses are the same as instruction addresses they really aren't because they can't share the same actual address. That is why you distinguish the two by starting the instruction addresses at 2000 in the example and variable addresses at 1000, right?

Steve: Right. A particular memory location can hold only one data item at a time. As far as RAM is concerned, machine instructions are just another kind of data. Therefore, if a particular location is used to store one data item, you can't store anything else there at the same time, whether it's instructions or data.

The last statement, j = i + 3; is the most complicated statement in our program, and it's not that complicated. As with the previous statement, it's executable, which means we need to generate machine instructions to execute it. Because we haven't changed ax since we used it to initialize the variable i with the value 5, it still has that value. Therefore, to calculate the value of j, we can just add 3 to the value in ax by executing the instruction add ax,3. After the execution of this instruction, ax will contain i + 3. Now all we have to do is to store that value in j. As indicated in the translation of the statement short j; the address used to hold the value of j is 1002. Therefore, we can set j to the value in ax by executing the instruction mov [1002],ax.

Figure compile4 shows what the "memory map" looks like now.

Compiling, part 4 (Figure compile4)

Address    Variable Name

1000         i                
1002         j

Address    Machine Instruction        Assembly Language                 
                                      Equivalent                
      +--------------------+                 
2000  |  b8 05 00          |    mov ax,5                
      +--------------------+                 
2003  |  a9 00 10          |    mov [1000],ax                
      +--------------------+                 
2006  |  05 03 00          |    add ax,3                
      +--------------------+                 
2009  |  a9 02 10          |    mov [1002],ax                
      +--------------------+ 

By the way, don't be misled by this example into thinking that all machine language instructions are 3 bytes in length. It's just a coincidence that all of the ones I've used here are of that length. The actual size of an instruction on the Intel CPUs can vary considerably, from 1 byte to a theoretical maximum of 12 bytes. Most instructions in common use, however, range from 1 to 5 bytes.

Here's the rest of the discussion that we had about this little exercise:

Susan: In this case mov means add, right?

Steve: No, mov means "move" and add means "add". When we write mov ax,5, it means "move the value 5 into the ax register". The instruction add ax,3 means "add 3 to the current contents of ax, replacing the old contents with this new value".

Susan: So you're moving 5 but adding 3? How do you know when to use mov and when to use add if they both kind of mean the same thing?

Steve: It depends on whether you want to replace the contents of a register without reference to whatever the contents were before (mov) or add something to the contents of the register (add).

Susan: OK, here is what gets me: how do you get from address 1000 and i=5 to ax? No, that's not it; I want you to tell me what is the relationship between ax and address 1000. I see ax as a register and that should contain the addresses, but here you are adding ax to the address. This doesn't make sense to me. Where are these places? Is address 1000 in RAM?

Steve: The ax register doesn't contain an address. It contains data. After the instruction mov ax,5, ax contains the number 5. After the instruction mov [1000],ax, memory location 1000 contains a copy of the 2-byte value in register ax; in this case, that is the value of the short variable i.

Susan: So do the machine addresses represent actual bytes?

Steve: The machine addresses specify the RAM locations where data (and programs) are stored.

Execution Is Everything

Having examined what the compiler does at compile time with the preceding little program fragment, the next question is what happens when the compiled program is executed at run time. When we start out, the sections of RAM we're concerned with will look like Figure ram1.

Before execution (Figure ram1)

  Register    Contents

  ax            ??

Address    Contents                Variable Name                
        +--------------------+                 
  1000  |  ?? ??             |    i                
        +--------------------+                 
  1002  |  ?? ??             |    j                
        +--------------------+ 

Address    Machine Instruction     Assembly Language                
                                     Equivalent                
        +--------------------+                 
* 2000  |  b8 05 00          |    mov ax,5                
        +--------------------+                 
  2003  |  a9 00 10          |    mov [1000],ax                
        +--------------------+                 
  2006  |  05 03 00          |    add ax,3                
        +--------------------+                 
  2009  |  a9 02 10          |    mov [1002],ax                
        +--------------------+

First, a couple of rules for this part of the "game":

  1. The asterisk on the left of the lower block indicates the next instruction to be executed.
  2. We put ?? in the variable and register contents to start out with, to indicate that we haven't stored anything in them yet, and so we don't know what they contain.

Now let's start executing the program. The first instruction, mov ax,5, as we saw earlier, means "set the contents of ax to the value 5".

Here's the situation after mov ax,5 is executed:

  Register    Contents

  ax            5

Address    Contents                Variable Name                
        +--------------------+                 
  1000  |  ?? ??             |    i                
        +--------------------+                 
  1002  |  ?? ??             |    j                
        +--------------------+ 

Address    Machine Instruction     Assembly Language                 
                                     Equivalent                
        +--------------------+                 
  2000  |  b8 05 00          |    mov ax,5                
        +--------------------+                 
* 2003  |  a9 00 10          |    mov [1000],ax                
        +--------------------+                 
  2006  |  05 03 00          |    add ax,3                
        +--------------------+                 
  2009  |  a9 02 10          |    mov [1002],ax                
        +--------------------+

As you can see, executing mov ax,5 has updated the contents of ax, and we've advanced to the next instruction.

When we have executed the next instruction, mov [1000],ax, the situation looks like this:

  Register    Contents

  ax            5

Address    Contents                Variable Name                
        +--------------------+                 
  1000  |  05 00             |    i                
        +--------------------+                 
  1002  |  ?? ??             |    j                
        +--------------------+ 

Address    Machine Instruction     Assembly Language                 
                                     Equivalent                
        +--------------------+                 
  2000  |  b8 05 00          |    mov ax,5                
        +--------------------+                 
  2003  |  a9 00 10          |    mov [1000],ax                
        +--------------------+                 
* 2006  |  05 03 00          |    add ax,3                
        +--------------------+                 
  2009  |  a9 02 10          |    mov [1002],ax                
        +--------------------+                
  

The situation after executing mov [1000],ax is just like the previous situation, except that the contents of location 1000 are now known, and of course we have moved to the next instruction.

Here's the result after the next instruction, add ax,3, is executed:

Register    Contents

  ax          8

Address    Contents                Variable Name                
        +--------------------+                 
  1000  |  05 00             |    i                
        +--------------------+                 
  1002  |  ?? ??             |    j                
        +--------------------+ 

Address    Machine Instruction     Assembly Language                 
                                     Equivalent                
        +--------------------+                 
  2000  |  b8 05 00          |    mov ax,5                
        +--------------------+                 
  2003  |  a9 00 10          |    mov [1000],ax                
        +--------------------+                 
  2006  |  05 03 00          |    add ax,3                
        +--------------------+                 
* 2009  |  a9 02 10          |    mov [1002],ax                
        +--------------------+

As expected, add ax,3 has increased the contents of ax by the value 3, leaving the result of 8. Now we're ready for the final instruction.

Here's the situation after the final instruction, mov [1002],ax, has been executed:

  Register    Contents

  ax            8

Address    Contents                Variable Name                
        +--------------------+                 
  1000  |  05 00             |    i                
        +--------------------+                 
  1002  |  08 00             |    j                
        +--------------------+ 

Address    Machine Instruction     Assembly Language                 
                                     Equivalent                
        +--------------------+                 
  2000  |  b8 05 00          |    mov ax,5                
        +--------------------+                 
  2003  |  a9 00 10          |    mov [1000],ax                
        +--------------------+                 
  2006  |  05 03 00          |    add ax,3                
        +--------------------+                 
  2009  |  a9 02 10          |    mov [1002],ax                
        +--------------------+

After executing the final instruction, mov [1002],ax, the variable i has the value 5 and the variable j has the value 8.

A Cast of Characters

This should give you some idea of how numeric variables and values work. But what about nonnumeric ones?

This brings us to the subject of two new variable types and the values they can contain. These are the char (short for "character") and its relative, the string. What are these good for, and how do they work?19 A variable of type char corresponds to 1 byte of storage. Since a byte has 8 bits, it can hold any of 256 (2^8) values; the exact values depend on whether it is signed or unsigned, as with the short variables we have seen before. Going strictly according to this description, you might get the idea that a char is just a "really short" numeric variable. A char indeed can be used for this purpose in cases where no more than 256 different numeric values are to be represented. In fact, this explains why you might want a signed char. Such a variable can be used to hold numbers from -128 to +127; an unsigned char, on the other hand, has a range from 0 to 255. This facility isn't used very much any more, but in the early days of C, memory was very expensive and scarce, so it was sometimes worth the effort to use 1-byte variables to hold small values.

However, the main purpose of a char is to represent an individual letter, digit, punctuation mark, "special character" (e.g., $, @, #, %, and so on) or one of the other "printable" and displayable units from which words, sentences, and other textual data such as this paragraph are composed.20 The 256 different possibilities are plenty to represent any character in English, as well as a number of other European languages; in fact, this is one of the main reasons that there are 8 bits in a byte, rather than some other number.

Of course, the written forms of "ideographic" languages such as Chinese and Korean consist of far more than 256 characters, so 1 byte isn't going to do the trick for these languages. While they have been supported to some extent by schemes that switch among a number of sets of 256 characters each, such clumsy approaches to the problem made programs much more complicated and error prone. As the international market for software is increasing rapidly, it has become more important to have a convenient method of handling large character sets; as a result, a standard method of representing the characters of such languages by using 2 bytes per character has been developed. It's called the "Unicode standard". There's even a proposed solution that uses 32 bits per character, for the day when Unicode doesn't have sufficient capacity; that should take care of any languages that alien civilizations might introduce to our planet.

Since one char isn't good for much by itself, we often use groups of them, called strings, to make them easier to handle. Just as with numeric values, these variables can be set to literal values, which represent themselves. Figure characters is an example of how to specify and use each of these types we've just encountered. This is the first complete program we've seen, so there are a couple of new constructs that I'll have to explain to you.

By the way, in case the program in Figure characters doesn't seem very useful, that's because it isn't; it's just an example of the syntax of defining and using variables and literal values. However, we'll use these constructs to do useful work later, so going over them now isn't a waste of time.

Some real characters and strings (code\basic00.cc) (Figure characters)

code/basic00.cc

Why do we need the line #include "string6.h"? Because we have to tell the compiler how to manipulate strings. They aren't built in to its knowledge base. For the moment, it's enough to know that the contents of the file string6.h are needed to tell the compiler how to use strings; we'll get into the details of this mechanism later, starting in Chapter string.htm.

However, since we're already on the subject of files, this would be a good time to point out that the two main types of files in C++ are implementation files (also known as source files), which in our case have the extension .cc, and header files, which by convention have the extension .h.21 Implementation files contain statements that result in executable code, while each header file contains information that allows us to access a set of language features. ain. We'll get into this in more detail in Chapter function.htm. For now, you'll just have to take my word that this is necessary; I promise I'll explain what it really means when you have enough background to understand the explanation.

You may also be puzzled by the function of the other statements in this program. If so, you're not alone. Let's see the discussion that Susan and I had about that topic.

Susan: Okay, in the example why did you have to write c2 = c1;? Why not B? Why make one thing the same thing as the other? Make it different. Why would you even want c2=c1; and not just say c1 twice, if that is what you want?

Steve: It's very hard to think up examples that are both simple enough to explain and realistic enough to make sense. You're right that this example doesn't do anything useful; I'm just trying to introduce what both the char type and the string type look like.

Susan: Come to think of it, what does c1='A'; have to do with the statement s1= "This is a test ";? I don't see any relationship between one thing and the other.

Steve: This is the same problem as the last one. They have nothing to do with one another; I'm using an admittedly contrived example to show how these variables are used.

Susan: I am glad now that your example of chars and strings (put together) didn't make sense to me. That is progress; it wasn't supposed to.

What does this useless but hopefully instructive program do? As is always the case, we have to tell the compiler what the types of our variables are before we can use them. In this case, c1 and c2 are of type char, whereas s1 and s2 are strings. After taking care of these formalities, we can start to use the variables. In the first executable statement, c1 = 'A'; we set the char variable c1 to a literal value, in this case a capital A; we need to surround this with single quotation marks (') to tell the compiler that we mean the letter A rather than a variable named A. In the next line, c2 = c1; we set c2 to the same value as c1 holds, which of course is 'A' in this case. The next executable statement s1 = "This is a test "; as you might expect, sets the string variable s1 to the value "This is a test ",22 which is a literal of a type called a C string. Don't confuse a C string literal with a string. A C string literal is a type of literal that we use to assign values to variables of type string. In the statement s1 = "This is a test "; we use a quotation mark, in this case the double quote ("), to tell the compiler where the literal value starts and ends.

You may be wondering why we need two different kinds of quotes in these two cases. The reason is that there are actually two types of nonnumeric data, fixed-length data and variable-length data. Fixed-length data are relatively easy to handle in a program, as the compiler can set aside the correct amount of space in advance. Variables of type char are 1 byte long and can thus contain exactly one character; as a result, when we set a char to a literal value, as we do in the line c1 = 'A'; the code that executes that statement has the simple task of copying exactly 1 byte representing the literal 'A' to the address reserved for variable c1.23

However, C string literals such as "This is a test " are variable-length data, and dealing with such data isn't so easy. Since there could be any number of characters in a C string, the code that does the assignment of a literal value like "This is a test " to a string variable has to have some way to tell where the literal value ends. One possible way to provide this needed information would be for the compiler to store the length of the C string literal in the memory location immediately before the first character in the literal. I would prefer this method; unfortunately, it is not the method used in the C language (and its descendant the C++ language). To be fair, the inventors of C didn't make an arbitrary choice; they had reasons for their decision on how to indicate the length of a string. You see, if we were to reserve only 1 byte to store the actual length in bytes of the character data in the string, then the maximum length of a string would be limited to 255 bytes. This is because the maximum value that could be stored in the length byte, as in any other byte, is 255. Thus, if we had a string longer than 255 bytes, we would not be able to store the length of the string in the 1 byte reserved for that purpose. On the other hand, if we were to reserve 2 bytes for the length of each string, then programs that contain many strings would take more memory than they should.

While the extra memory consumption that would be caused by using a 2-byte length code may not seem significant today, the situation was considerably different when C was invented. At that time, conserving memory was very important; the inventors of C therefore chose to mark the end of a C string by a byte containing the value 0, which is called a null byte.24 This solution has the advantage that only one extra byte is needed to indicate the end of a C string of any length. However, it also has some serious drawbacks. First, this solution makes it impossible to have a byte containing the value 0 in the middle of a C string, as all of the C string manipulation routines would treat that null byte as being the end of the C string. Second, it is a nontrivial operation to determine the length of a C string; the only way to do it is to scan through the C string until you find a null byte. As you can probably tell, I'm not particularly impressed with this mechanism; nevertheless, as it has been adopted into C++ for compatibility with C, we're stuck with it for literal strings in our programs.25 Therefore, the literal string "ABCD" would occupy 5 bytes, 1 for each character, and 1 for the null byte that the compiler adds automatically at the end of the literal. But we've skipped one step: How do we represent characters in memory? There's no intuitively obvious way to convert the character 'A' into a value that can be stored in 1 byte of memory.

The answer, at least for our purposes in English, is called the ASCII code standard. This stands for American Standard Code for Information Interchange, which as the name suggests was invented precisely to allow the interchange of data between different programs and makes of computers. Before the invention of ASCII, such interchange was difficult or impossible, since every manufacturer made up its own code or codes. Here are the specific character codes that we have to be concerned with for the purposes of this book:

  1. The codes for the capital letters start with hex 41 for 'A', and run consecutively to hex 5a for 'Z'
  2. The codes for the lower case letters start with hex 61 for 'a', and run consecutively to hex 7a for 'z'..26
  3. The codes for the numeric digits start with hex 30 for '0', and run consecutively to hex 39 for '9'.

Given these rules, the memory representation of the string "ABCD" might look something like Figure abcd.

Yet another small section of RAM (Figure abcd)

Address    Hex value

1000 41

1001 42

1002 43

1003 44

1004 00 (null byte; that is, end of C string)

Now that we see how strings are represented in memory, I can explain why we need two kinds of quotes. The double quotes tell the compiler to add the null byte at the end of the string literal, so that when the assignment statement s1 = "This is a test "; is executed, the program knows when to stop copying the value to the string variable.

A Byte by Any Other Name. . .

Have you noticed that I've played a little trick here? The illustration of the string "ABCD" should look a bit familiar; its memory contents are exactly the same as in Figure ram.value, where we were discussing numeric variables. I did this to illustrate an important point: the contents of memory actually consists of uninterpreted bytes, which have meaning only when used in a particular way by a program. That is, the same bytes can represent numeric data or characters, depending on how they are referred to.

This is one of the main reasons why we need to tell the C++ compiler what types our variables have. Some languages allow variables to be used in different ways at different times, but in C++ any given variable always has the same type; for example, a char variable can't change into a short. At first glance, it seems that it would be much easier for programmers to be able to use variables any way they like; why is C++ so restrictive?

The C++ type system, as this feature of a language is called, is specifically designed to minimize the risk of misinterpreting or otherwise misusing a variable. It's entirely too easy in some languages to change the type of a variable without meaning to; the resulting bugs can be very difficult to find, especially in a large program. In C++, the usage of a variable can be checked by the compiler. This static type checking allows the compiler to tell you about many errors that otherwise would not be detected until the program is running (dynamic type checking). This is particularly important in systems that need to run continuously for long periods of time. While you can reboot your machine if your word processor crashes due to a run-time error, this is not acceptable as a solution for errors in the telephone network, for example.

Of course, you probably won't be writing programs demanding that degree of reliability any time soon, but strict static type checking is still worthwhile in helping eliminate errors at the earliest possible stage in the development of our programs.

Some Strings Attached

After that infomercial for the advantages of static type checking, we can resume our examination of strings. You may have noticed that there's a space character at the end of the string "This is a test ". That's another reason why we have to use a special character like " (the double quote) to mark the beginning and end of a string; how else would the compiler know whether that space is supposed to be part of the string or not? The space character is one of the nonprinting characters (or nondisplay characters) that controls the format of our displayed or printed information; imagine how hard it would be to read this book without space characters! While we're on the subject, I should also tell you about some other characters that have special meaning to the compiler. They are listed in Figure specialchar.

Special characters for program text (Figure specialchar)

Name            Graphic    Use

Single quote ' surrounds a single character value

Double quote " surrounds a multi-character value

Semicolon ; ends a statement

Curly braces { } groups statements together

Parentheses ( ) surrounds part of a statement27

Backslash \ Tells the compiler that the next

character should be treated

differently from the way that

it would normally be treated.28

I compiled Figure specialchar at the instigation of guess who:

Susan: How about you line up all your cute little " ' \ ; things and just list their meanings? I forget what they are by the time I get to the next one. Your explanations of them are fine, but they are scattered all over the place; I just want one place that has all the explanations.

Steve: That's a good idea. As usual, you're doing a good job representing the novices; keep up the good work!

Our next task, after a little bit of practice with the memory representation of a C string, will be to see how we get the values of our strings to show up on the screen.

Exercises, Second Set

2. Assume that a C string literal starts at memory location 1001. If the contents of memory are as illustrated in Figure basex2, what is the value of the C string?

A small section of RAM (Figure basex2)

Address    Hex value

1000 44

1001 48

1002 45

1003 4c

1004 4c

1005 4f

1006 00

In and Out

Most programs need to interact with their users, both to ask them what they want and to present the results when they are available. The computer term for this topic is I/O (short for "input/output"). We'll start by getting information from the keyboard and displaying it on the screen; later, we'll go over the more complex I/O functions that allow us to read and write data on the disk.

Figure simple.output shows how to display the text "This is a test and so is this." as promised. The meaning of << is suggested by its arrowlike shape. The information on its right is sent to the "output target" on its left. In this case, we're sending the information to one of the predefined destinations, cout, which stands for "character output".29 Characters sent to cout are displayed on the screen.30

Some simple output (code\basic01.cc) (Figure simple.output)

code/basic01.cc

This program will send the following output to the screen:

This is a test and so is this.

So much for (simple) output. Input from the keyboard is just as simple. Let's modify our little sample to use it, as shown in Figure simple.input.

Some simple input and output (code\basic02.cc) (Figure simple.input)

code/basic02.cc

As you might have guessed, cin (shorthand for "character input") is the counterpart to cout, as >> is the counterpart to <<; cin supplies characters from the keyboard to the program via the >> operator.31 This program will wait for you to type in the first string, ended by hitting the ENTER key, then do the same for the second string. When you hit ENTER the second time, the program will display the first string, then a blank, and then the second string.

Susan had some questions about these little programs, beginning with the question of case sensitivity:

Susan: Are the words such as cout and cin case sensitive? I had capitalized a few of them just out of habit because they begin the sentence and I am not sure if that was the reason the compiler gave me so many error messages. I think after I changed them I reduced a few messages.

Steve: Everything in C++ is case sensitive. That includes keywords like if, for, do, and so on, as well as your own variables. That is, if you have a variable called Name and another one called name, those are completely different and unrelated to one another. You have to write cin and cout just as they appear here, or the compiler won't understand you.

If Only You Knew

In our examples so far, the program always executes the same statements in the same order. However, any real program is going to need to alter its behavior according to the data it is processing. For example, in a banking application, it might be necessary to send out a notice to a depositor whenever the balance in a particular account drops below a certain level; or perhaps the depositor would just be charged some exorbitant fee in that case. Either way, the program has to do something different depending on the balance. In particular, let's suppose that the "Absconders and Defaulters National Bank" has a minimum balance of $10,000. Furthermore, let's assume that if you have less than that amount on deposit, you are charged a $20 "service charge". However, if you are foolish enough to leave that ridiculous amount of money on deposit, then they will graciously allow you to get away with not paying them while they're using your money (without paying you interest, of course). To determine whether or not you should be charged for your checking account, the bank can use an if statement, as shown in Figure if.statement.

Using an if statement (code\basic03.cc) (Figure if.statement)

code/basic03.cc

This program starts by displaying the line

Please enter your bank balance:

on the screen. Then it waits for you to type in your balance, followed by the ENTER key (so it knows when you're done). The conditional statement checks whether you're a "good customer". If your balance is less than $10,000, the next statement is executed, which displays the line

Please remit $20 service charge.32

The phrase << endl is new here. It means "we're done with this line of output; send it out to the screen". You could also use the special character '\n', which means much the same thing; its official name is "newline".

Now let's get back to our regularly scheduled program. If the condition is false (that is, you have at least $10,000 in the bank), the computer skips the statement that asks you to remit $20; instead, it executes the one after the else, which tells you to have a nice day. That's what else is for; it specifies what to do if the condition specified in the if statement is false (that is, not true). If you typed in a number 10,000 or higher, the program would display the line

Have a nice day!

You don't have to specify an else if you don't want to. In that case, if the if condition isn't true, the program just goes to the next statement as though the if had never been executed.

While We're on the Subject

The while statement is another way of affecting the order of program execution. This conditional statement executes the statement under its control as long as a certain condition is true. Such potentially repeated execution is called a loop; a loop controlled by a while statement is called, logically enough, a while loop. Figure while is a program that uses a while loop to challenge the user to guess a secret number from 0 to 9, and keeps asking for guesses until the correct answer is entered.

Using a while statement (code\basic04.cc) (Figure while)

code/basic04.cc

There are a few wrinkles here that we haven't seen before. Although the while statement itself is fairly straightforward, the meaning of its condition != isn't intuitively obvious. However, if you consider the problem we're trying to solve, you'll probably come to the (correct) conclusion that != means "not equal", since we want to keep asking for more guesses while the Guess is not equal to our Secret number.33 Since there is a comparison operator that tests for "not equal", you might wonder how to test for "equal" as well; as is explained in some detail in the next chapter, in C++ we have to use == rather than = to compare whether two values are equal.

You might also be wondering whether an if statement with an else clause would serve as well as the while; after all, if is used to select one of two alternatives, and the else could select the other one. The answer is that this would allow the user to take only one guess before the program ends; the while loop lets the user try again as many times as needed to get the right answer.

Now you should have enough information to be able to write a simple program of your own. Susan asked for an assignment to do just that:

Susan: Based on what you have presented in the book so far, send me a setup, an exercise for me to try to figure out how to program, and I will give it a try. I guess that is the only way to do it. I can't even figure out a programmable situation on my own. So if you do that, I will do my best with it, and that will help teach me to think. (Can that be?) Now, if you do this, make it simple, and no tricks.

Of course, I did give her the exercise she asked for (exercise 3), but also of course, that didn't end the matter. She decided to add her own flourish, which resulted in exercise 4. These exercises follow below, right after some instructions on the mechanics of creating a program. The instructions assume that you've installed the software from the CD-ROM in the back of this book. Otherwise, follow the instructions in the back of the book to install the software, and then come back to these instructions.

  1. Change to the "\whos\code" directory on the drive where you installed the compiler.
  2. Type RHIDE to start the compiler's integrated development environment (IDE). Use RHIDE's editor to create a source code file containing the source code for your program. To do this, select the File menu and click New. Then type in the code for your program, which we'll assume will be called "myprog".
  3. When you get done writing the source code for your program, you will have to save it. To do this, select the File menu and click on Save. This will bring up a dialog box into which you will type the name of your file, which we'll assume is "myprog.cc" (without the quotes), and hit ENTER.
  4. Once you have written the source code for your program, you will need to create a "project" to keep track of the various parts of the program. To do this, select the "Project" menu, click on "Open project", and then type in the name of the project that you want to create. If you want to call your project "myprog", you'll type "myprog" (without the quotes) and hit ENTER.
  5. Now you will need to add the source file you just created to the project list so that it will be compiled. To do this, select the Project menu, then click on "Add item". Type in the name of your file, which is "myprog.cc", and hit ENTER.
  6. The next step is to tell RHIDE to include the files that define how strings and vectors work. You can skip this step if you aren't using any strings or vectors, but most programs will eventually need to use one or more of these data types even if they don't need them at the start, so you might as well include these files. To do this, select the Project menu, then click on "Add item". Type in "string7.cc" and hit ENTER, then type in "wassert.cc" and hit ENTER.
  7. When you have finished adding files to the project, click on the CANCEL button to close the "Add item" dialog box.
  8. To compile your program, select the "Compile" menu and click "Make". This will compile any source files that haven't been compiled since they were written or last changed.
  9. To run your program normally from a DOS prompt, make sure you are in the "\whos\code" directory, and then type the name of the program, without the extension. In this case, you would just type "myprog".
  10. To run your program under the debugger, make sure you are in the "\whos\code" directory, and then type "RHIDE myprog" (substituting the name of your program for "myprog"). Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger. The debugger also has a lot of other features, including displaying the values of variables during program execution, which I encourage you to explore.

Now here are the programs Susan came up with, along with some others that fall in the same category.

Exercises, Third Set

3. Write a program that asks the user to type in the number of people that are expected for dinner, not counting the user. Assuming that the number typed in is n, display a message that says "A table for (n+1) is ready.". For example, if the user types 3, display "A table for 4 is ready.".

4. Modify the program from exercise 3 to display an error message if the number of guests is more than 20.

5. Write a program that asks the user to type in his or her name and age. If the age is less than 47, then indicate that the user is a youngster; otherwise, that he or she is getting on in years.

6. Write a program that asks the user whether Susan is the world's most tenacious novice. If the answer is "true", then acknowledge the user's correct answer; if the answer is "false", then indicate that the answer is erroneous. If neither "true" nor "false" is typed in, chastise the user for not following directions.

7. Write a program that calculates how much extra allowance a teenager can earn by doing extra chores. Her allowance is calculated as $10 if she does no extra chores; she gets $1 additional for each extra chore she does.

Just up the Block

Our most recent programming example has contributed another item to our arsenal of programming weapons: namely, the ability to group several statements into one logical section of a program. That's the function of the curly braces, { and }. The first one of these starts such a section, called a block, and the second one ends the block. Because the two statements after the while are part of the same block, they are treated as a unit; both are executed if the condition in the while is true, and neither is executed if it is false. A block can be used anywhere that a statement can be used, and is treated in exactly the same way as if it were one statement.34

At the Fair

Now we're ready to write a program that vaguely resembles a solution to a real problem. We'll start with a simple, rural type of programming problem.

Imagine that you are at a county fair. The contest for the heaviest pumpkin is about to get underway, and the judges have asked for your help in operating the "pumpkin scoreboard". This device has a slot for the current pumpkin weight (the CurrentWeight slot), and another slot for the highest weight so far (the HighestWeight slot); each slot can hold three digits from 0 to 9 and therefore can indicate any weight from 0 to 999. The judges want you to maintain an up-to-date display of the current weight and of the highest weight seen so far. The weights are expressed to the nearest pound. How would you go about this task?

Probably the best way to start is by setting the number in both slots to the weight of the first pumpkin called out. Then, as each new weight is called out, you change the number in the CurrentWeight slot to match the current weight; if it's higher than the number in the HighestWeight slot, you change that one to match as well. Of course, you don't have to do anything to the HighestWeight slot when a weight less than the previous maximum weight is called out, because a pumpkin with a lesser weight can't be the winner. How do we know when we are done? Since a pumpkin entered in this contest has to have a weight of at least 1 pound, the weigher calls out 0 as the weight when the weighing is finished. At that point, the number in the HighestWeight slot is the weight of the winner.

The procedure you have just imagined performing can be expressed a bit more precisely by the following algorithm:

  1. Ask for the first weight.
  2. Set the number in the CurrentWeight slot to this value.
  3. Copy the number in the CurrentWeight slot to the HighestWeight slot.
  4. Display both the current weight and the highest weight so far (which are the same, at this point)
  5. While the CurrentWeight value is greater than 0 (that is, there are more pumpkins to be weighed), do steps 5a to 5d:
    1. Ask for the next weight.
    2. Set the number in the CurrentWeight slot to this weight.
    3. If the number in the CurrentWeight slot is greater than the number in the HighestWeight slot, copy the number in the CurrentWeight slot to the HighestWeight slot.
    4. Display the current weight and the highest weight so far.
  6. Stop. The number in the HighestWeight slot is the weight of the winner.

Now we're ready to look at the actual pumpkin-weighing program. You've already seen most of the constructs that the program contains, but let's examine the role of the preprocessor directive #include <iostream.h>. This tells the compiler that we want to use the standard C++ I/O library. The term preprocessor directive is a holdover from the days when a separate program called the preprocessor handled functions such as #include before handing the program over to the compiler; these days, these facilities are provided by the compiler, but the name has stuck.

The #include command has the same effect as copying all of the code from a file called iostream.h into our file; iostream.h defines the I/O functions and variables cout, cin, <<, and >>, along with others that we haven't used yet. If we left this line out, none of our I/O statements would work.

Figure pumpkin is the translation of our little problem into C++.

A C++ Program (code\pump1.cc) (Figure pumpkin)

  English                            C++

----------------------------------------------------------------------------------------

First, we have to tell the compiler what we're up to in this program

----------------------------------------------------------------------------------------

Define the |

standard input |

and output |

functionality | #include <iostream.h>

|

This is the main |

part of the program | int main()

|

Start of program | {

|

Define variables | short CurrentWeight;

| short HighestWeight;

|

----------------------------------------------------------------------------------------

Here's the start of the "working" code

----------------------------------------------------------------------------------------

|

Ask for the first |

weight | cout << "Please enter the first weight: ";

|

Set the number in |

the CurrentWeight |

slot to the value |

entered by the user | cin >> CurrentWeight;

|

Copy the number in |

the CurrentWeight |

slot to the |

HighestWeight slot | HighestWeight = CurrentWeight;

|

Display the current | cout << "Current weight " << CurrentWeight << endl;

and highest weights | cout << "Highest weight " << HighestWeight << endl;

|

While the number in |

the CurrentWeight |

slot is greater |

than 0 (i.e., there |

are more pumpkins |

to be weighed) | while (CurrentWeight > 0) |

Start repeated | {

steps |

|

Ask for the next |

weight | cout << "Please enter the next weight: ";

|

Set the number in |

the CurrentWeight |

slot to this value | cin >> CurrentWeight;

|

If the number in |

the CurrentWeight |

slot is more than |

the number in the |

HighestWeight slot, | if (CurrentWeight > HighestWeight)

|

then copy the |

number in the |

CurrentWeight slot |

to the | HighestWeight = CurrentWeight;

HighestWeight slot |

|

Display the current | cout << "Current weight " << CurrentWeight << endl;

and highest weights | cout << "Highest weight " << HighestWeight << endl;

|

End repeated steps |

in while loop | }

|

----------------------------------------------------------------------------------------

We've finished the job; now to clean up

----------------------------------------------------------------------------------------

Tell the rest of |

the system we're |

okay return 0;

|

End of program | }

----------------------------------------------------------------------------------------

Susan had some questions about variable names.

Susan: Tell me again what the different shorts mean in this figure. I am confused, I just thought a short held a variable like i. What is going on when you declare HighestWeight a short? So do the "words" HighestWeight work in the same way as i?

Steve: A short is a variable. The name of a short is made up of one or more characters; the first character must be a letter or an underscore (_), while any character after the first must be either a letter, an underscore, or a digit from 0 to 9. To define a short, you write a line that gives the name of the short. This is an example: short HighestWeight;

Susan: OK, but then how does i take 2 bytes of memory and how does HighestWeight take up 2 bytes of memory? They look so different, how do you know that HighestWeight will fit into a short?

Steve: The length of the names that you give variables has nothing to do with the amount of storage that the variables take up. After the compiler gets through with your program, there aren't any variable names; each variable that you define in your source program is represented by the address of some area of storage. If the variable is a short, that area of storage is 2 bytes long; if it's a char, the area of storage is 1 byte long.

Susan: Then where do the names go? They don't go "into" the short?

Steve: A variable name doesn't "go" anywhere; it tells the compiler to set aside an area of memory of a particular length that you will refer to by a given name. If you write short xyz; you're telling the compiler that you are going to use a short (that is, 2 bytes of memory) called xyz.

Susan: If that is the case, then why bother defining the short at all?

Steve: So that you (the programmer) can use a name that makes sense to you. Without this mechanism, you'd have to specify everything as an address. Isn't it easier to say

HighestWeight = CurrentWeight;

rather than

mov ax,[1000]

mov [1002],ax

or something similar?

Susan also had a question about the formatting of the output statement cout << "Highest weight " << HighestWeight << endl;.

Susan: Why do we need both "Highest weight" and HighestWeight in this line?

Steve: Because "Highest weight" is displayed on the screen to tell the user that the following number is supposed to represent the highest weight seen so far. On the other hand, HighestWeight is the name of the variable that holds that information, so including HighestWeight in the output statement will result in displaying the highest weight we've seen so far on the screen. Of course, the same analysis applies to the next line, which displays the label "Current weight" and the value of the variable CurrentWeight.

The topic of #include statements was the cause of some discussion with Susan. Here's the play by play:

Susan: Is the include command the only time you will use the # symbol?

Steve: There are other uses for #, but you won't see any of them for a long time, if ever.

Susan: So #include is a command.

Steve: Right; it's a command to the compiler.

Susan: Then what are the words we have been using for the most part called? Are those just called code or just statements? Can you make a list of commands to review?

Steve: The words that are defined in the language, such as if, while, for, and the like are called keywords. User defined names such as function and variable names are called identifiers.

Susan: So iostream.h is a header file telling the compiler that it is using info from the iostreams library?

Steve: Essentially correct; to be more precise, when we include iostream.h, we're telling the compiler to look into iostream.h for definitions that we're going to use.

Susan: Then the header file contains the secondary code of machine language to transform cin and cout into something workable?

Steve: Close, but not quite right. The machine code that makes cin and cout do their thing is in the iostreams library; the header file gives the compiler the information it needs to compile your references to cout, cin, <<, and >> into references to the machine code in the library.

Susan: So the header file directs the compiler to that section in the library where that machine code is stored? In other words, it is like telling the compiler to look in section XXX to find the machine code?

Steve: The header file tells the compiler what a particular part of the library does, while the library contains the machine code that actually does it.

If you're a programmer in some other language than C, you may wonder why we have to tell the compiler that we want to use the standard I/O library. Why doesn't the compiler know to use that library automatically? This seeming oversight is actually the result of a decision made very early in the evolution of C: to keep the language itself (and therefore the compiler) as simple as possible, adding functionality with the aid of standard libraries. Since a large part of the libraries can be written in C, this decision reduces the amount of work needed to "port" the C language from one machine architecture or operating system to another. Once the compiler has been ported, it's not too difficult to get the libraries to work on the new machine. In fact, even the C (or C++) compiler can be written in C (or C++), which makes the whole language quite portable. This may seem impossible. How do you get started? In fact, the process is called bootstrapping, from the impossible task of trying to lift yourself by your own bootstraps.35 The secret is to have one compiler that's already running; then you use that compiler to compile the compiler for the new machine. Once you have the new compiler running, it is common to use it to compile itself, so that you know it's working. After all, a compiler is a fairly complex program, so getting it to compile and execute properly is a pretty good indication that it's producing the right code.

Most of the rest of the program should be fairly easy to understand, except for the two lines int main() and return 0;, which have related functions. Let's start with the line int main(). As we've already seen, the purpose of the main() part of this line is to tell the compiler where to start execution; the C++ language definition specifies that execution always starts at a block called main. This may seem redundant, as you might expect the compiler to assume that we want to start execution at the beginning of the program. However, C++ is intended to be useful in the writing of very large programs; such programs can and usually do consist of several implementation files, each of which contains some of the functionality of the program. Without such a rule, the compiler wouldn't know which module should be executed first.

The int part of this same line specifies the type of the exit code that will be returned from the program by a return statement when the program is finished executing; in this case, that type is int. The exit code can be used by a batch file to determine whether our program finished executing correctly; an exit code of 0, by convention, means that it did.36 The final statement in the program is return 0;. This is the return statement just mentioned, whose purpose is to return an exit code of 0 when our program stops running. The value that is returned, 0, is an acceptable value of the type we declared in the line int main(), namely, int; if it didn't match, the compiler would tell us we had made an error.

Finally, the closing curly brace, }, tells the compiler that it can stop compiling the current block, which in this case is the one called main. Without this marker, the compiler would tell us that we have a missing }, which of course would be true.

Novice Alert

Susan decided a little later in our collaboration that she wanted to try to reproduce this program just by considering the English description, without looking at my solution. She didn't quite make it without peeking, but the results are illuminating nevertheless.

Susan: What I did was to cover your code with a sheet of paper and just tried to get the next line without looking, and then if I was totally stumped then I would look. Anyway, when I saw that if statement then I knew what the next statement would be but I am still having problems with writing backwards. For example

if (CurrentWeight > HighestWeight)

HighestWeight = CurrentWeight;

That is just so confusing because we just want to say that if the current weight is higher than the highest weight, then the current weight will be the new highest weight, so I want to write CurrentWeight = HighestWeight. Anyway, when I really think about it I know it makes sense to do it the right way; I'm just having a hard time thinking like that. Any suggestions on how to think backward?

Steve: What that statement means is "set HighestWeight to the current value of CurrentWeight. The point here is that = does not mean "is equal to"; it means "set the variable to the left of the = to the value of the expression to the right of the =". It's a lousy way of saying that, but that's what it means.

Susan: With all the { and } all over the place, I was not sure where and when the return 0; came in. So is it always right before the last }? OK, now that I think about it, I guess it always would be.

Steve: You have to put the return statement at a place where the program is finished whatever it was doing. That's because whenever that statement is executed, the program is going to stop running. Usually, as in this case, you want to do that at the physical end of the program.

Susan: Anyway, then maybe I am doing something wrong, and I am tired, but after I compiled the program and ran it, I saw that the HighestWeight label was run in together with the highest number and the next sentence, which said "Please enter the next weight". All those things were on the same line and I thought that looked weird; I tried to fix it but the best I had the stamina for at the moment was to put a space between the " and the P, to at least make a separation.

Steve: It sounds as though you need some endls in there to separate the lines.

Take It for a Spin

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program by following these steps:

  1. Change to the "code" subdirectory of the directory where you copied the example programs. If you've been following the directions as written, this will be "c:\whos\code".
  2. Start the Integrated Development Environment (IDE) for the compiler by typing its name, RHIDE, followed by the name of the program to be compiled. For example, to compile "pump1", type

    RHIDE pump1

  3. Then select "Make" from the "Compile" menu. The compiled version will be placed in the "\whos\code" directory.
  4. Once you are done compiling, you can exit to DOS by selecting the File menu and clicking on Exit. Then you can execute the program by typing the name of the program you want to run at the DOS prompt, first making sure that you are in the directory where you copied the example programs. For example, to run "pump1", exit to DOS, make sure you are in "\whos\code" and type:

    pump1

  5. To run your program under the debugger, make sure you are in the "\whos\code" directory, and then type "RHIDE pump1". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger. The debugger also has a lot of other features, including displaying the values of variables during program execution, which I encourage you to explore.

By the way, if you're confused about the seemingly meaningless values that the debugger shows for variables before the first statement that sets each one to a value, let me assure you that they are indeed meaningless. I'll explain why that is in the next chapter.

We're almost done with this chapter, but first let's practice a little more with chars and strings.

Exercises, Fourth Set

8. Here are four possible versions of an output statement. Assuming that the value of the string variable called name is "Joe Smith", what does each one of them do?

cout << "That is very old, " << name << ". " << endl;

cout << "That is very old, " << name << '. ' << endl;

cout << "That is very old, " << name << "." << endl;

cout << "That is very old, " << name << '.' << endl;

Now it's time for some review on what we've covered in this chapter.

Review

We started out by discussing the tremendous reliability of computers; whenever you hear "it's the computer's fault", the overwhelming likelihood is that in fact the software is to blame rather than the hardware. Then we took a look at the fact that, although computers are calculating engines, many of the functions for which we use them don't have much to do with numeric calculations; for example, the most common use of computers is probably word processing, which doesn't use much in the way of addition or subtraction. Nevertheless, we started out our investigation of programming with numeric variables, which are easier to understand than non-numeric ones. To use variables, we need to write a C++ program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into machine instructions.

That led us into a discussion of why and how our C++ program is translated into machine instructions by a compiler. We examined an example program that contained simple source code statements, including some that define variables and others that use those variables and constants to calculate results. We covered the symbols that are used to represent the operations of addition, subtraction, multiplication, division, and assignment, which are +, -, *, /, and = respectively. While the first four of these should be familiar to you, the last one is a programming notion rather than a mathematical one. This may be confusing because the operation of assignment is expressed by the = sign, but is not the same as mathematical equality. For example, the statement x = 3; does not mean "x is equal to 3", but rather "set the variable x to the value 3. After this discussion of the structure of statements in C++, we started an exploration of how the CPU actually stores and manipulates data in memory. The topics covered in this section included the order in which multibyte data items are stored in memory and the use of general registers to manipulate data efficiently.

Then we spent some time pretending to be a compiler, to see how a simple C++ program looks from that point of view, in order to improve our understanding of what the compiler does with our programs. This exercise involved keeping track of the addresses of variables and instructions and watching the effect of the instructions on the general registers and memory locations. During this exploration of the machine, we got acquainted with the machine language representation of instructions, which is the actual form that our executable programs take in memory. After a thorough examination of what the compiler does with our source code at compile time, we followed what would happen to the registers and memory locations at run time (that is, if the sample program were actually executed).

Then we began to look at two data types that can hold nonnumeric data, namely the char and the string. The char corresponds to 1 byte of storage, and therefore can hold one character of data. Examples of appropriate values for a char variable include letters (a-z, A-Z), digits (0-9), and special characters (e.g., , . ! @ # $ %). A char can also represent a number of other "nonprintable" characters such as the "space", which causes output to move to the next character position on the screen. Actually, a char can also be used as a "really short" numeric variable, but that's mostly a holdover from the days when memory was a lot more expensive, and every byte counted.

One char isn't much information, so we often want to deal with groups of them as a single unit; an example would be a person's name. This is the province of the string variable type: variables of this type can handle an indefinitely long group of chars.

At the beginning of our sample program for strings and chars, we encountered a new construct, the #include statement. This tells the compiler where to find instructions on how to handle data types such as strings, about which it doesn't have any built-in knowledge. Then we came across the line int main(), which indicates where we want to start executing our program. A C++ program always starts execution at the place indicated by such a line. We also investigated the meaning of int, which is the return type of main. The return type tells the compiler what sort of data this program returns to the operating system when it finishes executing; the return value can be used to determine what action a batch file should take next.

As we continued looking at the sample program for strings and chars, we saw how to assign literal values to both of these types, and noticed that two different types of quotes are used to mark off the literal values: the single quote ('), which is used in pairs to surround a literal char value consisting of exactly one char, such as 'a'; and the double quote ("), which is used in pairs to surround a literal string value of the C string type, such as "This is a test". We also investigated the reason for these two different types of literal values, which involves the notion of a null byte (a byte with the value 0); this null byte is used to mark the end of a C string in memory.

This led us to the discussion of the ASCII code, which is used to represent characters by binary values. We also looked at the fact that the same bytes can represent either a numeric value or a C string, depending on how we use those bytes in our program. That's why it's so important to tell the compiler which of these possibilities we have in mind when we write our programs. The way in which the compiler regulates our access to variables by their type, which is defined at compile time, is called the type system; the fact that C++ uses this static type checking is one of the reasons that C++ programs can be made more robust than programs written in languages that use dynamic type checking, where these errors are not detected until run time.

After a short discussion of some of the special characters that have a predefined meaning to the compiler, we took an initial glance at the mechanisms that allow us to get information into and out of the computer, known as I/O. We looked at the << function, which provides display on the screen when coupled with the built-in destination called cout. Immediately afterwards, we encountered the corresponding input function >> and its partner cin, which team up to give us input from the keyboard.

Next, we went over some program organization concepts, including the if statement, which allows the program to choose between two alternatives; the while statement, which causes another statement to be executed while some condition is true; and the block, which allows several statements to be grouped together into one logical statement. Blocks are commonly used to enable several statements to be controlled by an if or while statement.

At last we were ready to write a simple program that does something resembling useful work, and we did just that. The starting point for this program, as with all programs, was to define exactly what the program should do; in this case, the task was to keep track of the pumpkin with the highest weight at a county fair. The next step was to define a solution to this problem in precise terms. Next, we broke the solution down into steps small enough to be translated directly into C++. Of course, the next step after that was to do that translation. Finally, we went over the C++ code, line by line, to see what each line of the program did.

Now that the review is out of the way, we're about ready to continue with some more C++. First, though, let's step back a bit and see where we are right now.

Conclusion

We've come a long way from the beginning of this chapter. Starting from basic information on how the hardware works, we've made it through our first actual, runnable program. By now, you should have a much better idea whether you're going to enjoy programming (and this book). Assuming you aren't discouraged on either of these points, let's proceed to gather some more tools, so we can undertake a bigger project.

Answers to Exercises

  1. 3c43. In case you got a different result, here's a little help:

    a. If you got the result 433a, you started at the wrong address.

    b. If you got the result 433c, you had the bytes reversed.

    c. Finally, if you got 3a43, you made both of these mistakes.

    If you made one or more of these mistakes, don't feel too bad; even experienced programmers have trouble with hexadecimal values once in awhile. That's one reason we use compilers and assemblers rather than writing everything in hex!

  2. "HELLO". If you couldn't figure out what the "D" at the beginning was for, you started at the wrong place.
  3. Figure first.dinner is Susan's answer to this problem.

First dinner party program (code\basic05.cc) (Figure first.dinner)

code/basic05.cc

By the way, the reason that this program uses two lines to produce the sentence "Please type in the number of guests of your dinner party." is so that the program listing will fit on the page properly. If you prefer, you can combine those into one line that says cout << "Please type in the number of guests of your dinner party. ";. Of course, this also applies to the next exercise.

Susan: I would have sent it sooner had I not had the last cout arrows going like this >> (details).<G> Also, it just didn't like the use of endl; at the end of the last cout statement. It just kept saying "parse error".

Steve: If you wrote something like

cout << "A table for " << n+1 << "is ready. " << "endl;"

then it wouldn't work for two reasons. First, "endl;" is just a character string, not anything recognized by <<. Second, you're missing a closing ;, because characters inside quotes are treated as just plain characters by the compiler, not as having any effect on program structure.

The correct way to use endl in your second output statement is as follows:

cout << "A table for " << n+1 << "is ready. " << endl;

By the way, you might want to add a " " in front of the is in is ready, so that the number doesn't run up against the is. That would make the line look like this:

cout << "A table for " << n+1 << " is ready. " << "endl;"

Susan: Okay.

4. Figure second.dinner is Susan's answer to this problem, followed by our discussion.

Second dinner party program (code\basic06.cc) (Figure second.dinner)

code/basic06.cc

Steve: Congratulations on getting your program to work!

Susan: Now, let me ask you this: can you ever modify else? That is, could I have written else (n>20), or does else always stand alone?

Steve: You can say something like Figure else.if.

else if example (Figure else.if)

if (x < y)

{

cout << "x is less than y" << endl;

else

{

if (x > y)

cout << "x is greater than y" << endl;

else

cout << "x must be equal to y!" << endl;

}

}

In other words, the controlled block of an if statement or an else statement can have another if or else inside it. In fact, you can have as many "nested" if or else statements as you wish; however, it's best to avoid very deep nesting because it tends to confuse the next programmer who has to read the program.

5. The answer to this problem should look like Figure name.age.

Name and age program (code\basic07.cc) (Figure name.age)

code/basic07.cc

One point that might be a bit puzzling in this program is why it's not necessary to add an << endl to the end of the lines that send data to cout before we ask the user for input. For example, in the sequence:

cout << "What is your name? ";

cin >> name;

how do we know that the C string "What is your name? " has been displayed on the terminal before the user has to type in the answer? Obviously, it would be hard for the user to answer our request for information without a clue as to what we're asking for.

As it happens, this is a common enough situation that the designers of the iostreams library have anticipated it and solved it for us. When we use that library to do output to the screen and input from the keyboard, we can be sure that any screen output we have already requested will be displayed before any input is requested from the user via the keyboard.

6. Figure novice shows Susan's program, which is followed by our discussion.

Novice program (code\basic08.cc) (Figure novice)

code/basic08.cc

Susan: Steve, look at this. It even runs!

Also, I wanted to ask you one more question about this program. I wanted to put double quotes around the words true and false in the 3rd output statement because I wanted to emphasize those words, but I didn't know if the compiler could deal with that so I left it out. Would that have worked if I had?

Steve: Not if you just added quotes, because " is a special character that means "beginning or end of C string". Here's what you would have to do to make it work:

cout << "Please answer with either \"true\" or \"false\".";

The \ is a way of telling the compiler to treat the next character differently from its normal usage. In this case, we are telling the compiler to treat the special character " as "not special"; that is, \" means "just the character double quote, please, and no nonsense". This is called an escape, because it allows you to get out of the trap of having a " mean something special. We also use the \ to tell the compiler to treat a "nonspecial" character as "special"; for example, we use it to make up special characters that don't have any visual representation. You've already seen '\n', the "newline" character, which means "start a new line on the screen".

Susan: So if we want to write some character that means something "special", then we have to use a \ in front of it to tell the compiler to treat it like a "regular" character?

Steve: Right.

Susan: And if we want to write some character that is "regular" and make it do something "special", then we have to use a \ in front of it to tell the compiler that it means something "special"? That's weird.

Steve: It may be weird, but that's the way it works.

Susan: I now just got it. I was going to say, why would you put the first quotation mark before the slash, but now I see. Since you are doing a endline character, you have to have quotes on both sides to surround it which you don't usually have to do because the first quotes are usually started at the beginning of the sentence, and in this case the quote was already ended. Ok, thanks for clearing that up.

Steve: You've got it.

Susan: Another thing I forgot is how you refer to the statements in () next to the "if" keywords; what do you call the info that is in there?

Steve: The condition.

7. Figure allowance is Susan's version of this program. Actually, it was her idea in the first place.

Allowance program (code\basic09.cc) (Figure allowance)

code/basic09.cc

8. You'll be happy (or at least unsurprised) to hear that Susan and I had quite a discussion about this problem.

Susan: Remember on my "test" program how I finally got that period in there? Then I got to thinking that maybe it should have been surrounded by single quotes ' instead of double quotes. It worked with a double quote but since it was only one character it should have been a single quote, so I went back and changed it to a single quote and the compiler didn't like that at all. So I put it back to the double. So what is the deal?

Steve: You should be able to use 'x' or "x" more or less interchangeably with <<, because it can handle both of those data types (char and C string, respectively). However, they are indeed different types. The first one specifies a literal char value, whereas the second specifies a literal C string value. A char value can only contain one character, but a C string can be as long as you want, from none to hundreds or thousands of characters.

Susan: Here's the line that gave me the trouble:

cout << "That is very old, " << name << ". " << endl;

Remember I wanted to put that period in at the end in that last line? It runs like this but not with the single quotes around it. That I don't understand. This should have been an error. But I did something right by mistake <G>. Anyway, is there something special about the way a period is handled?

Steve: I understand your problem now. No, it's not the period; it's the space after the period. Here are four possible versions of that line:

1. cout << "That is very old, " << name << ". " << endl;

2. cout << "That is very old, " << name << '. ' << endl;

3. cout << "That is very old, " << name << "." << endl;

4. cout << "That is very old, " << name << '.' << endl;

None of these is exactly the same as any of the others. However, 1, 3, and 4 will do what you expect, whereas 2 will produce weird looking output, with some bizarre number where the . should be. Why is this? It's not because . is handled specially, but because the space (" "), when inside quotes, either single or double, is a character like any other character. Thus, the expression '. ' in line 2 is a "multicharacter constant", which has a value dependent on the compiler; in this case, you'll get a short value equal to (256 * the ASCII value of the period) + the ASCII value of the space. This comes out to 11808, as I calculate it. So the line you see on the screen may look like this:

That is very old, Joe Smith11808

Now why do all of the other lines work? Well, 1 works because a C string can have any number of characters and be sent to cout correctly; 3 works for the same reason; and 4 works because '.' is a valid one-character constant, which is another type that << can handle.

I realize it's hard to think of the space as a character, when it doesn't look like anything; in addition, you can add spaces freely between variables, expressions, and so forth, in the program text. However, once you're dealing with C strings and literal character values, the space is just like any other character.

Susan: So it is okay to use single characters in double quotes? If so, why bother with single quotes?

Steve: Single quotes surround a literal of type char. This is a 1-byte value that can be thought of (and even used) as a very short number. Double quotes surround a literal of type "C string". This is a multibyte value terminated by a 0 byte, which cannot be used or treated as a number.

Susan: I am not too clear on what exactly the difference is between the char and "C string". I thought a char was like a alpha letter, and a string was just a bunch of letters.

Steve: Right. The difference is that a C string is variable length, and a char isn't; this makes a lot of difference in how they can be manipulated.

Susan: Am I right in thinking that a char could also be a small number that is not being used for calculations?

Steve: Or that is used for (very small) calculations; for instance, if you add 1 to the value 'A', you get the value for 'B'. At least that's logical.

Susan: What do you mean by "terminated by a 0 byte"? That sounds familiar; was that something from an earlier chapter which is now ancient history?

Steve: Yes, we covered that some time ago. The way the program can tell that it's at the end of a C string (which is of variable length, remember) is that it gets to a byte with the value 0. This is a crummy way to specify the size of a variable-length string, in my opinion, but it's too late to do anything about it; it's built into the compiler.

Susan: When you say a C string, do you mean the C programming language in contrast to other languages?

Steve: Yes.

Susan: All right, then the 0 byte used to terminate a C string is the same thing as a null byte?

Steve: Yes.

Susan: Then you mean that each C string must end in a 0 so that the compiler will know when to stop processing the data for the string?

Steve: Yes.

Susan: Could you also just put 0? Hey, it doesn't hurt to ask. I don't see the problem with the word hello; it ends with an o and not a 0. But what if you do need to end the sentence with a 0?

Steve: It's not the digit 0, which has the ASCII code 30h, but a byte with a 0 value. You can't type in a null byte directly, although you can create one with a special character sequence if you want to. However, there's no point in doing that usually, because all literal C strings such as "hello" always have an invisible 0 byte added automatically by the compiler. If for some reason you need to explicitly create a null byte, you can write it as '\0', as in

char x = '\0';

which emphasizes that you really mean a null byte and not just a plain old 0 like this:

char x = 0;

The difference between these two is solely for the benefit of the next programmer to look at your code; they're exactly the same to the compiler.

Footnotes

  1. Please note that capitalization counts in C++, so IF and WHILE are not the same as if and while. You have to use the latter versions.
  2. However, we haven't yet eliminated the possibility of hardware errors, as the floating-point flaw in early versions of the PentiumTM processor illustrates. In rare cases, the result of the divide instruction in those processors was accurate to only about 5 decimal places rather than the normal 16 to 17 decimal places.
  3. This was apparently against the plan administrator's principles.
  4. How is the compiler itself translated into machine language so it can be executed? The most common method is to write the compiler in the same language it compiles and use the previous version of the compiler to compile the newest version! Of course, this looks like an infinite regress; how did the first compiler get compiled? By manual translation into assembly language, which was then translated by an assembler into machine language. To answer the obvious question, at some point an assembler was coded directly in machine language.
  5. The compiler also does a lot of other work for us, which we'll get into later.
  6. By the way, blank lines are ignored by the compiler; in fact, because of the trailing semicolon on each statement, you can even run all the statements together on one line if you want to, without confusing the compiler. However, that will make it much harder for someone reading your code later to understand what you're trying to do. Programs aren't written just for the compiler's benefit but to be read by other people; therefore, it is important to write them so that they can be understood by those other people. One very good reason for this is that more often than you might think, those "other people" turn out to be you, six months later.
  7. The // marks the beginning of a comment, which is a note to you or another programmer; it is ignored by the compiler. For those of you with BASIC experience, this is just like REM (the "remark" keyword in that language); anything after it on a line is ignored.
  8. Other kinds of variables can hold larger (and smaller) values; we'll go over them in some detail in future chapters.
  9. At the risk of boring experienced C programmers, let me reiterate that = does not mean "is equal to"; it means "set the variable to the left of the = to the value of the expression to the right of the =. In fact, there is no equivalent in C++ to the mathematical notion of equality. We have only the assignment operator = and the comparison operator ==, which we will encounter in the next chapter. The latter is used in if statements to determine whether two expressions have the same value. All of the valid comparison operators are listed in Figure comparisonfig.
  10. If you have any programming experience whatever, you may think that I'm spending too much effort on this very simple point. I can report from personal experience that it's not necessarily easy for a complete novice to grasp. Furthermore, without a solid understanding of the difference between an algebraic equality and an assignment statement, that novice will be unable to understand how to write a program.
  11. Besides these general registers, a dedicated register called esp plays an important role in the execution of real programs. We'll see how it does this in Chapter function.htm.
  12. This is not the only possible solution to this problem nor necessarily the best one; for example, in many Motorola CPUs, you specify the length of the variable directly in the instruction, so loading a word (i.e., 2-byte) variable might be specified by the instruction move.w, where the .w means "word". Similarly, a longword (i.e., 4-byte) load might be specified as move.l, where the .l means "long word".
  13. It's also possible to load a 2-byte value into a 32-bit (i.e., 4-byte) register such as eax and have the high part of that register set to 0 in one instruction, by using an instruction designed specifically for that purpose. This approach has the advantage that further processing can be done with the 32-bit registers.
  14. The number inside the brackets [ ] represents a memory address.
  15. As I've mentioned previously, blank lines are ignored by the compiler; you can put them in freely to improve readability.
  16. However, I've cheated here by using small enough numbers in the C++ program that they are the same in hex as in decimal.
  17. The real compiler on the CD-ROM actually uses 4-byte addresses, but this doesn't change any of the concepts involved.
  18. These addresses are arbitrary; a real compiler will assign addresses to variables and machine instructions by its own rules.
  19. In case you were wondering, the most common pronunciation of char has an a like the a in "married", while the ch sounds like "k".
  20. As we will see shortly, not all characters have visible representations; some of these "nonprintable" characters are useful in controlling how our printed or displayed information looks.
  21. Other compilers sometimes use other extensions for implementation files, such as .cpp, and for header files, such as .hpp.
  22. Please note that there is a space (blank) character at the end of that C string literal, after the word "test". That space is part of the literal value.
  23. Warning: Every character inside the quotes has an effect on the value of the literal, whether the quotes are single or double; even "invisible" characters such as the space (' ') will change the literal's value. In other words, the line c1 = 'A'; is not the same as the line c1 = 'A ';. The latter statement may or may not be legal, depending on the compiler you're using, but it is virtually certain not to give you what you want, which is to set the variable c1 to the value equivalent to the character 'A'. Instead, c1 will have some weird value resulting from combining the 'A' and the space character. In the case of a string value contained in double quotes, multiple characters are allowed, so "A B" and "AB" both make sense, but the space still makes a difference; namely, it keeps the 'A' and 'B' from being next to one another.
  24. I don't want to mislead you about this notion of a byte having the value 0; it is not the same as the representation of the decimal digit "0". As we'll see, each displayable character (and a number of invisible ones) is assigned a value to represent it when it's part of a string or literal value (i.e., a C string literal or char literal). The 0 byte I'm referring to is a byte with the binary value 0.
  25. Happily, we can improve on it in most other circumstances, as you'll see later.
  26. You may wonder why I have to specify that the codes for each case of letters run consecutively. Believe it or not, there are a number of slightly differing codes collectively called EBCDIC (Extended Binary Coded Decimal Interchange Code), in which this is not true! Eric Raymond's amusing and interesting book, The New Hacker's Dictionary, has details on this and many other historical facts.
  27. I'll be more specific later, when we have seen some examples.
  28. For example, if you wanted to insert a " in a string, you would have to use \", because just a plain " would indicate the end of the string. That is, if you were to set a string to the value "This is a \"string\".", it would display as: This is a "string".
  29. The line #include <iostream.h> is necessary here to tell the compiler about cout and how it works. We'll get into this in a bit more detail later in this chapter.
  30. By the way, cout is pronounced "see out".
  31. Similarly to cout, cin is pronounced "see in" rather than "sin".
  32. This explanation assumes that the "10,000" is the balance in dollars. Of course, this doesn't account for the possibility of balances that aren't a whole number of dollars, and there's also the problem of balances greater than $32,767, which wouldn't fit into a short. I'll mention possible solutions to these problems in Appendix .
  33. You may be wondering why we need parentheses around the expression Guess != Secret. The conditional expression has to be in parentheses so that the compiler can tell where it ends and the statement to be controlled by the while begins.
  34. If you look at someone else's C++ program, you're likely to see a different style for lining up the {} to indicate where a block begins and ends. As you'll notice, my style puts the { and } on separate lines rather than running them together with the code they enclose, to make them stand out, and indents them further than the conditional statement. I find this the clearest, but this is a matter where there is no consensus. The compiler doesn't care how you indent your code or whether you do so at all; it's a stylistic issue.
  35. If this term sounds familiar, we've already seen it in the context of how we start up a computer when it's turned on, starting from a small boot program in the ROM, or Read-Only Memory.
  36. A batch file is a text file that directs the execution of a number of programs, one after the other, without manual intervention. A similar facility is available in most operating systems.

Return to the table of contents